function ngdomEngine(options) {
	this.debug = false;
	this.game = 'default';
	this.homeUrl = '';
	this.enableAudio = true;
	this.mobile = false;
	this.deck = null;
	this.portal = null;
	this.view = null;
	this.portalSize = { width: 320, height: 240 };
	this.portalEdgeBuffer = 50;
	this.wallGridSize = { width: 40, height: 30 };
	this.wallGridScale = 8;
	this.map = null;
	this.player = null;
	this.objects = [];
	this.ui = false;
	this.builder = false;
	this.mute = false;
	this.ambience = {};
	this.sounds = {};
	this.pause = false;
	this.input = { running: false, speed: 0, direction: 2, keyPresses: {}, directionStack: [] };
	this.suspendInput = false;
	this.interval = 25;
	this.begun = false;
	this.fullyLoaded = false;
	this.defaultMap = 'default.map';
	this.defaultTransition = 'pan';
	this.spriteCache = {};
	this.initClasses = { ui: 'ngdomUI', builder: 'ngdomBuilder', map: 'ngdomMap', sound: 'ngdomSound', 
		soundManager: 'ngdomSoundManager', player: 'ngdomSpritePlayer' };
	
	if (options) $.extend(true, this, options);
}
ngdomEngine.prototype.init = function () {
	document.title = document.title.replace('{#version}', ngdom.version);
	document.title = document.title.replace('{#build}', ngdom.build);
	
	var basePath = this.homeUrl + 'game/' + this.game + '/';
	this.paths = {
		game: 	basePath,
		audio:	basePath + 'audio/',
		image:	basePath + 'image/',
		map:	basePath + 'map/',
		scene:	basePath + 'scene/',
		script:	basePath + 'script/',
		sprite:	basePath + 'sprite/'
	};
	
	this.deck = $('<div id="deck"></div>');
	this.portal = $('<div id="portal"></div>').appendTo(this.deck);
	this.view = $('<div id="view"></div>').appendTo(this.portal);
	this.backgroundPlate = $('<div id="background-plate"></div>').appendTo(this.view);
	this.background0 = $('<img id="background0" alt="" />').hide().appendTo(this.backgroundPlate);
	this.background = $('<img id="background" alt="" />').hide().appendTo(this.backgroundPlate);
	$('body').append($('<div id="screen"></div>').append(this.deck));
	
	this.mobile = this.mobile || !!('ontouchstart' in window);
	if (this.mobile) this.initMobile();
	
	$.each(this.initClasses, function (key, className) {
		ngdom.Engine.initClasses[key] = window[className];
		ngdom.Engine.initClasses[key+'Name'] = className;
	});

	this.ui = new this.initClasses.ui();
	if (typeof(this.initClasses.builder) == 'function') this.builder = new this.initClasses.builder();
	
	if (this.debug) $('<div id="debug-pos"></div>').appendTo(this.deck);

	$(document).keydown(this.keydown);
	$(document).keyup(this.keyup);
	$(document).keydown(this.ui.keydown);
	$(document).keyup(this.ui.keyup);
	if (this.builder) {
		$(document).keydown(this.builder.keydown);
		$(document).keyup(this.builder.keyup);
	}
	
	this.ambience = new ngdom.Engine.initClasses.soundManager();
	this.sounds = new ngdom.Engine.initClasses.soundManager();
	if (this.enableAudio) {
		this.mute = $.cookie('ngdom-mute') == '1';
		$.getScript('lib/soundmanager2-nodebug-jsmin.js', function () {
			soundManager.url = 'lib/swf/';
			soundManager.flashVersion = 9;
			soundManager.useFlashBlock = false;
			soundManager.debugMode = false;
			soundManager.onready(function () {
				if (ngdom.Engine.mute) soundManager.mute();
				
				ngdom.Engine.loadAudio();
				
				if (ngdom.Engine.ambientAutoPlay)
					$.each(ngdom.Engine.ambientAutoPlay, function (index, key) {
						ngdom.Engine.ambience.play(key);
					});
			});
			soundManager.beginDelayedInit();
		});
	}
	else 
		this.mute = true;
	
	if (ngdom.Utils.queryValue('reset')) this.ui.menuChangePassword();
	else if (ngdomData.authorized) this.ui.menuContinue();
	else this.ui.menuNewGame();
	
	if (ngdom.browser.iOS && !window.navigator.standalone) {
		this.ui.wait('Tap the "+" icon and choose "Add to Home Screen" to install Cellarfold.');
	}

	this.loadMap(ngdomData.savegame && ngdomData.savegame.mapfile ? ngdomData.savegame.mapfile : this.defaultMap);
};
ngdomEngine.prototype.initMobile = function () {
	
	$('body').addClass('mobile');
	this.enableAudio = false;
	$.fx.interval = 50;
	
	if (ngdom.browser.iOS) {
	}
	if (ngdom.browser.Android) {
    	window.scrollTo(0, 0);
    	var nPageH = $(document).height();
    	var nViewH = window.outerHeight;
    	if (nViewH > nPageH) {
      		nViewH = nViewH / window.devicePixelRatio;
      		$('body').css('height', nViewH + 'px');
    	}
    	window.scrollTo(0, 1);
 	}

	this.touches = {};
	this.dpad = $('<div id="dpad"></div>').appendTo(this.deck);
	document.addEventListener('touchmove', function (event) { ngdom.Engine.touchHandler(event); event.preventDefault(); }, false);
	document.addEventListener('touchstart', this.touchHandler, false);
	document.addEventListener('touchend', this.touchHandler, false);
	
	this.buttonRun = $('<img id="btn-run" src="image/btn-run.gif" alt="" />').appendTo(this.deck);
	this.buttonAttack = $('<img id="btn-attack" src="image/btn-attack.gif" alt="" />').appendTo(this.deck)
		.bind('touchstart', function (event) { ngdom.Engine.keydown({ which: 32 }); })
		.bind('touchend', function (event) { ngdom.Engine.keyup({ which: 32 }); });;
};
ngdomEngine.prototype.touchHandler = function (event) { 
	var dpadpos = ngdom.Engine.dpad.offset();
	var runpos = ngdom.Engine.buttonRun.offset();
	var runPress = false;
	var nowTouching = {};
	var debuginfo = dpadpos.left+', '+dpadpos.top;
	debuginfo += ' :: '+event.touches.length;
	$.each(event.touches, function (index, touch) {
		debuginfo += ', ('+touch.screenX+', '+touch.screenY+')';
		if (touch.screenX >= dpadpos.left && touch.screenX <= dpadpos.left + 100
			&& touch.screenY >= dpadpos.top && touch.screenY <= dpadpos.top + 100)
		{
			var touchOffset = {
				x: touch.screenX - (dpadpos.left + 50),
				y: touch.screenY - (dpadpos.top + 50)
			};
			if (Math.abs(touchOffset.x) > Math.abs(touchOffset.y) && touchOffset.x >= 0) //right
				key = 39;
			else if (Math.abs(touchOffset.x) > Math.abs(touchOffset.y) && touchOffset.x < 0) //left
				key = 37;
			else if (touchOffset.y >= 0) //down
				key = 40;
			else //up
				key = 38;
				
			ngdom.Engine.touches[key] = true;
			nowTouching[key] = true;
				
			debuginfo += ' dpad!';
		}
		else if (touch.screenX >= runpos.left && touch.screenX <= runpos.left + 48
			&& touch.screenY >= runpos.top && touch.screenY <= runpos.top + 44)
		{
			runPress = true;
			debuginfo += ' running!';
		}
	});
	$.each(ngdom.Engine.touches, function (key, wasTouching) {
		if (nowTouching[key])
			ngdom.Engine.keydown({ which: key, shiftKey: runPress });
		if (wasTouching && !nowTouching[key]) {
			ngdom.Engine.keyup({ which: key, shiftKey: runPress });
			ngdom.Engine.touches[key] = false;
		}
	});
	if (ngdom.Engine.debug) $('#debug-pos').html(debuginfo);
	if (ngdom.Engine.begun) event.preventDefault();
};
ngdomEngine.prototype.loadAudio = function () {
	this.ambience.load('music', 'music.mp3', { loop: true, playInMenu: false });
	
	this.sounds.load('playerAttack', 'player_attack.mp3');
	this.sounds.load('playerDies', 'player_dies.mp3');
	this.sounds.load('hit', 'hit.mp3');
	this.sounds.load('hurt', 'hurt.mp3');
	this.sounds.load('kill', 'kill.mp3');
	this.sounds.load('lowHealth', 'low_health.mp3', { loop: true });
	this.sounds.load('block', 'block.mp3');
};
ngdomEngine.prototype.loadMap = function (mapfile) {
	$.ajax({ url: this.paths.map + mapfile, dataType: 'text', 
	  beforeSend: function(request) {
		if (typeof(request.overrideMimeType) == 'function')
			request.overrideMimeType("text/plain");
	  },
	  success: function(data) {
		  ngdom.Engine.onMapLoaded(data);
	  } 
	});
};
ngdomEngine.prototype.onMapLoaded = function (mapdata) {
	var now = new Date();
	this.tickCount = now.getTime();
	
	this.map = new this.initClasses.map(this);
	eval(this.initClasses.mapName+'.prototype.load = function () '+mapdata+';');
	this.map.load();
	this.map.initialPosition = $.extend({}, this.map.position);
	this.map.rebornPosition = $.extend({}, this.map.position);
	this.player = new this.initClasses.player();
	this.player.position = $.extend({}, this.map.playerStartPosition);
	this.player.rebornPosition = $.extend({}, this.player.position);
	this.map.grid[this.map.position.x][this.map.position.y].visited = true;
	if (this.map.defaultTransition) this.defaultTransition = this.map.defaultTransition;
	if (this.debug) $('#debug-pos').html(this.map.position.x+', '+this.map.position.y);

	this.loadGame();
	this.player.initSprites();
	this.player.setSprite();
	this.ui.initHud();

	if (this.begun) this.startLoop();
	this.fullyLoaded = true;
};
ngdomEngine.prototype.startLoop = function () {
	this.ui.initHints();
	this.begun = true;
	$.each(this.ambience.sounds, function (key, audio) {
		if (!audio.playInMenu && audio.playStatus) audio.play();
	});
	
	this.loop();
};
ngdomEngine.prototype.enterScene = function (early, scene) {
	if (typeof(scene) == 'undefined') 
		scene = ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y];
	$.each(scene.objects, 
		function (index, object) { 
			if (typeof(early) == 'undefined' || 
			  (early && object.arriveEarlyStayLate) || 
			  (!early && !object.arriveEarlyStayLate))
				object.enterScene(); 
		});
};
ngdomEngine.prototype.exitScene = function (late, scene) {
	if (typeof(scene) == 'undefined') 
		scene = ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y];
	$.each(scene.objects, 
		function (index, object) { 
			if (typeof(late) == 'undefined' ||
			  (late && object.arriveEarlyStayLate) || 
			  (!late && !object.arriveEarlyStayLate))
				object.exitScene(); 
		});
};
ngdomEngine.prototype.switchScene = function (xOffset, yOffset) {
	if (xOffset == 0 && yOffset == 0) return false;
	
	this.sceneChange = true;
	var oldScene = this.map.grid[this.map.position.x][this.map.position.y];
	this.exitScene(false);
	if (this.builder) this.builder.switchScene();
	
	var direction = (xOffset) ? 3 - xOffset : 2 + yOffset;
	var transition = this.map.grid[this.map.position.x][this.map.position.y].transitions[direction];
	if (!transition) transition = this.defaultTransition;

	this.map.position.x += xOffset;
	this.map.position.y += yOffset;
	if (this.debug) $('#debug-pos').html(this.map.position.x+', '+this.map.position.y);
	this.map.grid[this.map.position.x][this.map.position.y].visited = true;
	this.map.loadImages();
	
	this.objects = [];
	$('.temp').remove();
	
	var loadBg = new Image();
	
	switch(transition) {
	case 'cut':
		
		var pos = this.player.el.position();
		this.player.el
			.animate({
				top: pos.top+(yOffset * 10),
				left: pos.left+(xOffset * 10)
			}, {
				duration: 500, 
				complete: function () {
					ngdom.Engine.background.attr('src', 
						ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].img.src);
						
					ngdom.Engine.exitScene(true, oldScene);
					ngdom.Engine.enterScene(true);
					
					ngdom.Engine.player.el
						.css({
							top: pos.top+(-yOffset * (ngdom.Engine.portalSize.height + 10)),
							left: pos.left+(-xOffset * (ngdom.Engine.portalSize.width + 10))
						})
						.animate({
							top: pos.top+(-yOffset * (ngdom.Engine.portalSize.height - 10)),
							left: pos.left+(-xOffset * (ngdom.Engine.portalSize.width - 10))
						}, {
							duration: 500, 
							complete: function () {
								ngdom.Engine.enterScene(false);
								ngdom.Engine.sceneChange = false;
								ngdom.Engine.loop();
								$('.obj.early').removeClass('early').addClass('late');
							}
						});
				}
			});
		break;
		
	case 'fade':
		
		this.player.el
			.animate({
				top: '+='+(yOffset * 10),
				left: '+='+(xOffset * 10)
			}, {
				duration: 500,
				queue: false
			});
		this.portal.css({ background: 'black' });
		this.view.fadeTo(800, 0, function () { 
			ngdom.Engine.exitScene(true, oldScene);
			ngdom.Engine.enterScene(true);
			var pos = ngdom.Engine.player.el.position();
			ngdom.Engine.player.el
				.css({
					top: pos.top+(-yOffset * (ngdom.Engine.portalSize.height + 20)),
					left: pos.left+(-xOffset * (ngdom.Engine.portalSize.width + 20))
				})
				.animate({
					top: pos.top+(-yOffset * (ngdom.Engine.portalSize.height - 0)),
					left: pos.left+(-xOffset * (ngdom.Engine.portalSize.width - 0))
				}, {
					duration: 500,
					queue: false
				});
			ngdom.Engine.background
				.attr('src', ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].img.src);
			ngdom.Engine.view
				.fadeTo(800, 1, function () {
					ngdom.Engine.enterScene(false);
					ngdom.Engine.sceneChange = false;
					ngdom.Engine.loop();
				});
			$('.obj.early').removeClass('early').addClass('late');
		});
		break;
	
	case 'dissolve':
	
		this.player.el
			.animate({
				top: '+='+(yOffset * 10),
				left: '+='+(xOffset * 10)
			}, 500);
		this.background.css({ zIndex:1 });
		this.background0.attr('src', this.map.grid[this.map.position.x][this.map.position.y].img.src)
			.css({ left:0, top:0 })
			.show();
		ngdom.Engine.enterScene(true);
		$('.obj.early').css({ zIndex:0 });
		
		var finished = false;
		this.background.add('.obj.late:visible').fadeOut(1200, function () { 
			if (finished) return;
			ngdom.Engine.exitScene(true, oldScene);
			var pos = ngdom.Engine.player.el.position();
			ngdom.Engine.player.el
				.css({
					top: pos.top+(-yOffset * (ngdom.Engine.portalSize.height + 20)),
					left: pos.left+(-xOffset * (ngdom.Engine.portalSize.width + 20))
				})
				.animate({
					top: pos.top+(-yOffset * (ngdom.Engine.portalSize.height - 0)),
					left: pos.left+(-xOffset * (ngdom.Engine.portalSize.width - 0))
				}, 500, function () {
					ngdom.Engine.sceneChange = false;
					ngdom.Engine.loop();
				});
			ngdom.Engine.enterScene(false);
			ngdom.Engine.background.css({ zIndex:'' })
				.load(function () { 
					$(this).show().unbind('load'); 
					ngdom.Engine.background0.hide(); 
				})
				.attr('src', ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].img.src);
			if (ngdom.Engine.background.get(0).complete) ngdom.Engine.background.load();
			$('.obj.early').removeClass('early').addClass('late');
			finished = true;
		});
		break;
		
	case 'pan':
	default:
	
		if (this.debug) console.log('pan');

		var loadfinished = false, finished = false;
		var onLoadBg = function () { 
			if (loadfinished) return;
			loadfinished = true;
			ngdom.Engine.background0.attr('src', loadBg.src).show(); 
			ngdom.Engine.backgroundPlate
				.add('.obj.early, .obj.late')
				.animate({
					top: '+='+(-yOffset * ngdom.Engine.portalSize.height),
					left: '+='+(-xOffset * ngdom.Engine.portalSize.width)
				}, 1500, function () {
					if (finished) return;
					finished = true;
					ngdom.Engine.background.load(function() { 
						$(this).unbind('load');
						ngdom.Engine.backgroundPlate.css({ left: '0px', top: '0px' });
						ngdom.Engine.background0.hide(); 
						ngdom.Engine.exitScene(true, oldScene);
						ngdom.Engine.enterScene(false);
						$('.obj.early').removeClass('early').addClass('late');
					})
					.attr('src', ngdom.Engine.background0.attr('src'));
				});
			var playerPositionAnimate = {};
			if (yOffset != 0) playerPositionAnimate.top = '+='+(-yOffset * (ngdom.Engine.portalSize.height - 10));
			if (xOffset != 0) playerPositionAnimate.left = '+='+(-xOffset * (ngdom.Engine.portalSize.width - 10));
			ngdom.Engine.player.el
				.animate(playerPositionAnimate, {
					duration: 1500, 
					complete: function () {
						ngdom.Engine.sceneChange = false;
						ngdom.Engine.loop();
					},
					queue: false
				});
		};
		this.background0.css({
			left: (xOffset * this.portalSize.width)+'px',
			top: (yOffset * this.portalSize.height)+'px'
		});
		
		//alert(loadBg.complete);
		loadBg.onload = onLoadBg;
		loadBg.src = this.map.grid[this.map.position.x][this.map.position.y].img.src;
		//if (loadBg.complete) onLoadBg();
		
		this.enterScene(true);
		$('.obj.early').hide().animate({
				top: '+='+(yOffset * this.portalSize.height),
				left: '+='+(xOffset * this.portalSize.width)
			}, 1, function () { $(this).show(); });
		
	}

	ngdom.Engine.player.position.x += (-xOffset * (ngdom.Engine.portalSize.width - 10));
	ngdom.Engine.player.position.y += (-yOffset * (ngdom.Engine.portalSize.height - 10));
};
ngdomEngine.prototype.addProjectile = function (projectile) {
	this.objects[this.objects.length] = projectile;
	if (projectile.el) projectile.el.addClass('temp');
};
ngdomEngine.prototype.loop = function () {

	var now = new Date();
	this.tickCount = now.getTime();

	if (this.sceneChange) return;
	if (this.pause) {
		setTimeout(function () { ngdom.Engine.loop(); }, this.interval);
		return;
	}

	if (!this.player.dying && !this.player.dead)
	{
		if (ngdom.Engine.player.speed >= 0 && !this.suspendInput)
		{
			ngdom.Engine.player.running = ngdom.Engine.input.running;
			ngdom.Engine.player.direction = ngdom.Engine.input.direction;
			ngdom.Engine.player.speed = ngdom.Engine.input.speed;
		}
		
		$.each(this.map.grid[this.map.position.x][this.map.position.y].objects, function (index, object) {
			if (ngdom.Engine.tickCount - object.sceneStartTime > object.sceneDelay) {
				object.loop();
				object.behave();
				object.move(ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].walls);
			}
		});
		$.each(this.objects, function (index, object) {
			object.loop();
			object.behave();
			object.move(ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].walls);
		});
		
		this.player.loop();
		this.player.move();
		if (this.player.position.x >= this.portalSize.width) this.switchScene(1, 0);
		else if (this.player.position.x <= 0) this.switchScene(-1, 0);
		else if (this.player.position.y >= this.portalSize.height) this.switchScene(0, 1);
		else if (this.player.position.y <= 0) this.switchScene(0, -1);
	}
	
	if (this.player.dying)
	{
		if (this.tickCount - this.player.dyingStartTime > this.player.dyingLength)
		{
			this.player.dying = false;
			this.player.dead = true;
			//this.player.direction = 3;
			this.player.setSprite();
			this.player.el.fadeOut(1500).delay(500, this.rebirth);
		}
	}
	
	var next = this.interval - (new Date().getTime() - this.tickCount);
	setTimeout(function () { ngdom.Engine.loop(); }, next);
};
ngdomEngine.prototype.keydown = function (e) {
	if (!ngdom.Engine.player || !ngdom.Engine.begun) return;
	if (ngdom.Engine.pause || ngdom.Engine.suspendInput) return;
	if (!ngdom.Engine.player.dying && !ngdom.Engine.player.dead)
	{
		ngdom.Engine.input.keyPresses[e.which] = true;
		ngdom.Engine.input.running = e.shiftKey;
	
		if (e.which == 37 || e.which == 85) ngdom.Engine.input.direction = 4; //left
		if (e.which == 38 || e.which == 75) ngdom.Engine.input.direction = 1; //up
		if (e.which == 39 || e.which == 78) ngdom.Engine.input.direction = 2; //right
		if (e.which == 40 || e.which == 72) ngdom.Engine.input.direction = 3; //down

		if (e.which == 37 || e.which == 38 || e.which == 39 || e.which == 40 || 
			e.which == 85 || e.which == 75 || e.which == 78 || e.which == 72) 
		{
			if (ngdom.Engine.input.directionStack.indexOf(ngdom.Engine.input.direction) > -1)
				ngdom.Engine.input.directionStack.splice(
					ngdom.Engine.input.directionStack.indexOf(ngdom.Engine.input.direction), 1
					);
			ngdom.Engine.input.directionStack.push(ngdom.Engine.input.direction);
			ngdom.Engine.input.speed = ngdom.Engine.input.running ? 
				ngdom.Engine.player.runSpeed : ngdom.Engine.player.moveSpeed;
			return false;
		}
				
		//for shift press while walking
		if (ngdom.Engine.input.running && ngdom.Engine.input.speed == ngdom.Engine.player.moveSpeed) {
			ngdom.Engine.input.speed = ngdom.Engine.player.runSpeed;
			return false;
		}
		
		if (!ngdom.Engine.player.attacking && !ngdom.Engine.player.wounded)
		{
			if (e.which == 17 || e.which == 32)
			{
				ngdom.Engine.player.attack();
				ngdom.Engine.sounds.play('playerAttack');
			}
		}
	}
	//if (e.which == 32) $('#minimap').remove();
	//if (e.which == 32) ngdom.Engine.player.gainXP(100);
};
ngdomEngine.prototype.keyup = function (e) {
	ngdom.Engine.input.keyPresses[e.which] = false;
	ngdom.Engine.input.running = e.shiftKey;
	//for shift release while running
	if (!ngdom.Engine.input.running && ngdom.Engine.input.speed == ngdom.Engine.player.runSpeed)
		ngdom.Engine.input.speed = ngdom.Engine.player.moveSpeed;
		
	if (e.which == 37 || e.which == 38 || e.which == 39 || e.which == 40 || 
		e.which == 85 || e.which == 75 || e.which == 78 || e.which == 72) 
	{
		if (ngdom.Engine.input.directionStack.length > 0) {
			if (ngdom.Engine.input.directionStack.pop() == ngdom.Engine.input.direction) {
				if (ngdom.Engine.input.directionStack.length > 0) {
					var dir = ngdom.Engine.input.directionStack.pop();
					if (((ngdom.Engine.input.keyPresses[37] || ngdom.Engine.input.keyPresses[85]) && dir == 4) ||
						((ngdom.Engine.input.keyPresses[38] || ngdom.Engine.input.keyPresses[75]) && dir == 1) ||
						((ngdom.Engine.input.keyPresses[39] || ngdom.Engine.input.keyPresses[78]) && dir == 2) ||
						((ngdom.Engine.input.keyPresses[40] || ngdom.Engine.input.keyPresses[72]) && dir == 3)
						)
					{
						ngdom.Engine.input.direction = dir;
					}
					ngdom.Engine.input.directionStack.push(dir);
				}
			}
		}
	}
	
	if (ngdom.Engine.player.speed >= 0)
	{
		if ((e.which == 37 || e.which == 85) && ngdom.Engine.input.direction == 4) ngdom.Engine.input.speed = 0;
		if ((e.which == 38 || e.which == 75) && ngdom.Engine.input.direction == 1) ngdom.Engine.input.speed = 0;
		if ((e.which == 39 || e.which == 78) && ngdom.Engine.input.direction == 2) ngdom.Engine.input.speed = 0;
		if ((e.which == 40 || e.which == 72) && ngdom.Engine.input.direction == 3) ngdom.Engine.input.speed = 0;
	}
};
ngdomEngine.prototype.saveGame = function (resurrect) {
	var foundCheckpoint = { distanceSquared: 1000000000000000, checkpoint: null, mapPosition: null };
	var grid = new Array();
	for (var x = 0; x < this.map.size.width; x++)
	{
		grid[x] = new Array();
		for (var y = 0; y < this.map.size.height; y++)
		{
			grid[x][y] = { obj: [] };
			if (this.map.grid[x][y].visited) grid[x][y].v = 1;
			for (var i = 0; i < this.map.grid[x][y].objects.length; i++)
			{
				grid[x][y].obj[i] = this.map.grid[x][y].objects[i].save();
				if (resurrect) this.map.grid[x][y].objects[i].resurrect();
				if (this.map.grid[x][y].objects[i].type == 'checkpoint' && this.map.grid[x][y].objects[i].active) {
					var pos = this.map.grid[this.map.position.x][this.map.position.y].spawnPosition || this.map.position;
					var ds = ngdom.Utils.distanceSquared(pos, { x: x, y: y });
					if (ds < foundCheckpoint.distanceSquared) {
						foundCheckpoint.distanceSquared = ds;
						foundCheckpoint.checkpoint = this.map.grid[x][y].objects[i];
						foundCheckpoint.mapPosition = { x: x, y: y };
					}
				}
			}
		}
	}
	
	var ambient = [];
	if (this.ambience && this.ambience.play)
		$.each(this.ambience.sounds, function (key, audio) { 
			if ($.isFunction(audio.isPlaying) && audio.isPlaying())
				ambient[ambient.length] = key;
		});
	
	var save = {
		mapPosition: foundCheckpoint.mapPosition,
		playerPosition: foundCheckpoint.checkpoint.playerRebornPosition(),
		playerState: this.player.save(),
		gridState: grid,
		ambience: ambient
	};
	
	if (ngdomData.authorized)
		$.post('game.php', { action: 'save', data: JSON.stringify(save) }, function (response) { 
			if (!response)
				ngdom.Engine.ui.showStatus('Save failed!');
			else
				ngdom.Engine.ui.showStatus('Game saved.');
		});
		
	return foundCheckpoint;
};
ngdomEngine.prototype.loadGame = function () {
	var loaded = this.fullyLoaded;
	this.fullyLoaded = false;
	var load = ngdomData.savedata;
	var initAmbience = [];
	$('.obj').remove();
	if (load) {
		if (typeof(load) == 'string') load = JSON.parse(load);
		this.map.position = $.extend({}, load.mapPosition);
		if (this.map.position.x >= this.map.size.width) this.map.position.x = this.map.size.width - 1;
		if (this.map.position.y >= this.map.size.height) this.map.position.y = this.map.size.height - 1;
		if (this.debug) $('#debug-pos').html(this.map.position.x+', '+this.map.position.y);
		this.player.position = $.extend({}, load.playerPosition || { x: 160, y: 120 });
		this.player.load(load.playerState);
		
		for (var x = 0; x < this.map.size.width; x++)
		{
			for (var y = 0; y < this.map.size.height; y++)
			{
				for (var i = 0; i < load.gridState[x][y].obj.length; i++)
				{
					if (this.map.grid[x][y].objects[i])
						this.map.grid[x][y].objects[i].load(load.gridState[x][y].obj[i]);
				}
				this.map.grid[x][y].visited = load.gridState[x][y].v;
			}
		}
		
		if ($.isArray(load.ambience)) 
			initAmbience = load.ambience;
	}
	else {
		this.map.load();
		this.map.position = $.extend({}, this.map.initialPosition);
		this.player = new this.initClasses.player();
		this.player.position = $.extend({}, this.map.playerStartPosition);
		initAmbience = this.map.initialAmbience || [];
	}
	
	if (this.ambience && this.ambience.play)
		$.each(initAmbience, function (index, ambient_key) {
			ngdom.Engine.ambience.play(ambient_key);
		});
	else
		this.ambientAutoPlay = initAmbience;
				
	this.map.loadImages();
	this.background.attr('src', this.map.grid[this.map.position.x][this.map.position.y].img.src).show();
	this.player.direction = 2;
	this.player.enterScene();
	this.enterScene();
	this.fullyLoaded = loaded;
};
ngdomEngine.prototype.intro = function () {
	this.begun = true;
	if (this.fullyLoaded) this.startLoop();
};
ngdomEngine.prototype.death = function () {
	ngdom.Engine.player.dying = true;
	ngdom.Engine.player.setSprite();
	ngdom.Engine.player.dyingStartTime = ngdom.Engine.tickCount;
	ngdom.Engine.player.speed = 0;
	ngdom.Engine.player.running = false;
	ngdom.Engine.input.running = false;
	ngdom.Engine.input.speed = 0;
	for (var i = 0; i < ngdom.Engine.objects.length; i++) {
		ngdom.Engine.objects[i].el.hide();
	}
	$('#portal').css('background', 'white');
	ngdom.Engine.background.fadeOut(ngdom.Engine.player.dyingLength);
	$('.obj:not(#player)').fadeOut(ngdom.Engine.player.dyingLength);
	ngdom.Engine.ambience.stop('music');
	ngdom.Engine.sounds.stop('lowHealth');
	ngdom.Engine.sounds.play('playerDies');
};
ngdomEngine.prototype.rebirth = function () {
	var checkpoint = ngdom.Engine.saveGame(true);
	checkpoint.checkpoint.playerOverlap = true;
	ngdom.Engine.map.position = $.extend({}, checkpoint.mapPosition);
	ngdom.Engine.player.position = $.extend({}, checkpoint.checkpoint.playerRebornPosition());
	ngdom.Engine.player.health = 30;
	if (ngdom.Engine.debug) $('#debug-pos').html(ngdom.Engine.map.position.x+', '+ngdom.Engine.map.position.y);
	
	ngdom.Engine.map.loadImages();
	ngdom.Engine.background.attr('src', 
		ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].img.src);
	ngdom.Engine.background.fadeIn(3000, function () { ngdom.Engine.ambience.fadeIn('music'); });
	
	ngdom.Engine.fullyLoaded = false;

	$('.obj').remove();
	$.each(ngdom.Engine.map.grid[ngdom.Engine.map.position.x][ngdom.Engine.map.position.y].objects, function (index, object) { 
		object.enterScene(); 
	});
	
	ngdom.Engine.fullyLoaded = true;

	ngdom.Engine.player.enterScene();
	ngdom.Engine.player.dead = false;
	ngdom.Engine.player.setSprite();
	ngdom.Engine.player.dead = true;
	ngdom.Engine.player.el.fadeIn(1500, function () { ngdom.Engine.player.dead = false; });
	ngdom.Engine.ui.updateHud(false);
};
ngdomEngine.prototype.miniMap = function () {
	var minimap = $('<div id="minimap"></div>').hide();
	for (var y=0; y<this.map.size.height; y++) {
		for (var x=0; x<this.map.size.width; x++) {
			$('<img src="" alt="" />')
				.attr('src', this.map.grid[x][y].visited ? this.map.imagePath+x+'x'+y+'.jpg' : 'images/spacer.gif')
				.data('position', { x: x, y: y })
				.click(function () {
					$('#minimap').remove();
				})
				.appendTo(minimap);
		}
	}
	$('<p class="hint">press space to close</p>').appendTo(minimap);
	minimap.appendTo(this.deck)
		.fadeIn(500)
		.animate({ width:'640px', height:'480px', marginTop:'-120px', marginLeft:'-160px' }, 
			{ duration:500, queue:false });
};
ngdomEngine.prototype.showGameText = function (text, cssClass) {
	if (!$('#ingame-text').length) {
		$('<div id="ingame-text"></div>').hide().appendTo(this.portal);
	}
	$('#ingame-text').html(text);
	if (cssClass) $('#ingame-text').addClass(cssClass);
	$('#ingame-text:not(:visible)').fadeIn(800);
};
ngdomEngine.prototype.hideGameText = function (text) {
	if (typeof(text) == 'undefined' || $('#ingame-text:visible').html() == text)
		$('#ingame-text:visible').fadeOut(800, function() { $('#ingame-text').attr('class', ''); });
};